package me.victor.hotfix

import android.content.Context
import android.os.Environment
import android.widget.Toast
import dalvik.system.DexClassLoader
import dalvik.system.PathClassLoader
import java.io.*
import java.lang.reflect.Array

/**
 *
 */
class HotFixEngine {
    /**
     * 获得pathList中的dexElements
     *
     * @param obj
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    fun getDexElements(obj: Any): Any {
        return getField(obj, obj.javaClass, DEX_ELEMENTS_FIELD)
    }

    interface LoadDexFileInterruptCallback {
        fun loadDexFile(file: File?): Boolean
    }

    /**
     * fix
     *
     * @param context
     */
    fun loadDex(context: Context?, dexFile: File) {
        if (context == null) {
            return
        }
        val fixDir = context.getDir(FIX_DEX_PATH, Context.MODE_PRIVATE)
        //mrege and fix
        mergeDex(context, fixDir, dexFile)
    }

    /**
     * 获取指定classloader 中的pathList字段的值（DexPathList）
     *
     * @param classLoader
     * @return
     */
    @Throws(
        ClassNotFoundException::class,
        NoSuchFieldException::class,
        IllegalAccessException::class
    )
    fun getDexPathListField(classLoader: Any?): Any {
        return getField(
            classLoader,
            Class.forName(DEX_BASECLASSLOADER_CLASS_NAME),
            DEX_PATHLIST_FIELD
        )
    }

    /**
     * 获取一个字段的值
     *
     * @return
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    fun getField(obj: Any?, clz: Class<*>, fieldName: String?): Any {
        val field = clz.getDeclaredField(fieldName!!)
        field.isAccessible = true
        return field[obj]
    }

    /**
     * 为指定对象中的字段重新赋值
     *
     * @param obj
     * @param claz
     * @param filed
     * @param value
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    fun setFiledValue(obj: Any?, claz: Class<*>, filed: String?, value: Any?) {
        val field = claz.getDeclaredField(filed!!)
        field.isAccessible = true
        field[obj] = value
        //        field.setAccessible(false);
    }

    /**
     * 合并dex
     *
     * @param context
     * @param fixDexPath
     */
    fun mergeDex(context: Context, fixDexPath: File, dexFile: File) {
        try {
            //创建dex的optimize路径
            val optimizeDir = File(fixDexPath.absolutePath, DEX_OPT_DIR)
            if (!optimizeDir.exists()) {
                optimizeDir.mkdir()
            }
            //找到dex并通过DexClassLoader去加载
            //dex文件路径，优化输出路径，null,父加载器
            val dexClassLoader = DexClassLoader(
                dexFile.absolutePath,
                optimizeDir.absolutePath,
                null,
                context.classLoader
            )
            //获取app自身的BaseDexClassLoader中的pathList字段
            val appDexPathList = getDexPathListField(context.classLoader)
            //获取补丁的BaseDexClassLoader中的pathList字段
            val fixDexPathList = getDexPathListField(dexClassLoader)
            val appDexElements = getDexElements(appDexPathList)
            val fixDexElements = getDexElements(fixDexPathList)
            //合并两个elements的数据，将修复的dex插入到数组最前面
            val finalElements = combineArray(fixDexElements, appDexElements)
            //给app 中的dex pathList 中的dexElements 重新赋值
            setFiledValue(
                appDexPathList,
                appDexPathList.javaClass,
                DEX_ELEMENTS_FIELD,
                finalElements
            )
            Toast.makeText(context, "修复成功!", Toast.LENGTH_SHORT).show()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    companion object {
        const val DEX_OPT_DIR = "optimize_dex" //dex的优化路径
        const val DEX_BASECLASSLOADER_CLASS_NAME = "dalvik.system.BaseDexClassLoader"
        const val DEX_FILE_E = "dex" //扩展名
        const val DEX_ELEMENTS_FIELD = "dexElements" //pathList中的dexElements字段
        const val DEX_PATHLIST_FIELD = "pathList" //BaseClassLoader中的pathList字段
        const val FIX_DEX_PATH = "fix_dex" //fixDex存储的路径

        /**
         * 两个数组合并
         *
         * @param arrayLhs
         * @param arrayRhs
         * @return
         */
        private fun combineArray(arrayLhs: Any, arrayRhs: Any): Any {
            val localClass = arrayLhs.javaClass.componentType
            val i = Array.getLength(arrayLhs)
            val j = i + Array.getLength(arrayRhs)
            val result = Array.newInstance(localClass, j)
            for (k in 0 until j) {
                if (k < i) {
                    Array.set(result, k, Array.get(arrayLhs, k))
                } else {
                    Array.set(result, k, Array.get(arrayRhs, k - i))
                }
            }
            return result
        }

        /**
         * 复制SD卡中的补丁文件到dex目录
         */
        fun copyDexFileToAppAndFix(context: Context, dexFileName: String?, copyAndFix: Boolean) {
            val path = File(Environment.getExternalStorageDirectory(), dexFileName)
            if (!path.exists()) {
                Toast.makeText(context, "没有找到补丁文件", Toast.LENGTH_SHORT).show()
                return
            }
            if (!path.absolutePath.endsWith(DEX_FILE_E)) {
                Toast.makeText(context, "补丁文件格式不正确", Toast.LENGTH_SHORT).show()
                return
            }
            val dexFilePath = context.getDir(FIX_DEX_PATH, Context.MODE_PRIVATE)
            val dexFile = File(dexFilePath, dexFileName)
            if (dexFile.exists()) {
                dexFile.delete()
            }
            //copy
            path.copyTo(dexFile, true)
            if (dexFile.exists() && copyAndFix) {
                //复制成功,进行修复
                HotFixEngine().loadDex(context, dexFile)
            }
        }
    }
}